<?php
/**
 * Db_Object strucrure config
 * @package Db
 * @subpackage Db_Object
 * @author Kirill A Egorov kirill.a.egorov@gmail.com 
 * @copyright Copyright (C) 2011-2012  Kirill A Egorov, 
 * DVelum project http://code.google.com/p/dvelum/ , http://dvelum.net
 * @license General Public License version 3
 */
class Db_Object_Config
{
    static protected $_instances = array();
    /**
     * @var Config_Abstract
     */
    protected $_config;       
    /** 
	 * Required fields
     * @static
     * @var array $required
     */
     static protected $_required = array('table','fields','engine');       
    /**
     * Additional fields config for objects under rev. control
     * @var array
     */
     static protected $_vcFields = null;
    /**
     * @param string $name
     * @return Db_Object_Config
     */
     protected $_name;
     /**
      * @param string $name - object name
      * @return Db_Object_Config
      */
    static public function getInstance($name){
    	$name = strtolower($name);
        if(!array_key_exists($name, self::$_instances))
            self::$_instances[$name] = new self($name);
            
         return self::$_instances[$name];   
    }
    final private function __clone(){}
    final private function __construct($name){
        $this->_name = $name;
        $this->_config = Config::factory(Config::File_Array, Db_Object::getConfigPath() . $name .'.php');
        $this->_loadProperties();
    }   
    
    /**
     * Add field
     * @param string $name
     * @param array $config
     */
    protected function _addField($name , array $config){
    	/**
    	 * Avoid indirect modification of overloaded element 
    	 * @todo optimize assignment
    	 */
    	$fields = $this->_config['fields'];
    	if(isset($fields[$name]))
    		trigger_error('Field ' . $name . ' already exists in '.$this->_name);
    	$fields['id'] = $config;
    	$this->_config->set('fields', $fields);    
    }
    /**
     * Get object Db table name (with prefix)
     * @return string
     */
    public function getTable(){
    	return Db_Object::getDbPrefix() . $this->_config['table'];
    }
    /**
     * Add index
     * @param string $name
     * @param array $config
     */
    protected function _addIndex($name , array $config){
    	/**
    	 * Avoid indirect modification of overloaded element 
    	 * @todo optimize assignment
    	 */
    	$indexes = $this->_config['indexes'];
    	if(isset($indexes[$name]))
    		trigger_error('Index ' . $name . ' already exists in '.$this->_name);
    	$indexes[$name] = $config;  
    	$this->_config->set('indexes', $indexes);   
    }
    
    /**
     * Prepare config, load system properties
     * @throws Exception
     */
    protected function _loadProperties()
    { 
        foreach (self::$_required as $key)
           if(!$this->_config->offsetExists($key))
                throw new Exception('Invalid Data Object config structure');    
        
        /*
         * System field init
         */       
        $this->_addField('id', array(
        	'title'=>'Primary key',
        	'system'=>true,
        	'db_type' => 'bigint',
            'db_isNull' => false,
            'db_unsigned'=>true,
        	'db_auto_increment'=>true,
        	'unique'=>'PRIMARY'
        )); 
        /*
         * System index init
         */        
       $this->_addIndex('PRIMARY', array(
        		'columns'=>array('id'),
        		'fulltext'=>false,
        		'unique'=>true,
       			'primary'=>true
       ));
        
        /*
         * Load additional fields for object uner revision control
         */
        if($this->isRevControl()){        
            if(is_null(self::$_vcFields)) 
                self::$_vcFields = Config::factory(Config::File_Array, Db_Object::getConfigPath().'vc'.DIRECTORY_SEPARATOR.'vc_fields.php')->__toArray();

            $this->_config['fields'] =  array_merge($this->_config['fields'] , self::$_vcFields);
        }                           
        
        $dataLink = & $this->_config->dataLink();
        foreach ($dataLink['fields'] as $k=>&$v)
        {
            if(isset($v['type']) && $v['type']==='link')
            {
                switch ($v['link_config']['link_type']){
                   
                    case 'multy':
                    	     $v['db_type'] = 'longtext';
					         $v['db_isNull'] = 0;
					         $v['db_default'] = '';                      
					    break; 
                    
                    case 'object':
                    		$v['db_type'] ='bigint';
				            $v['db_isNull'] = 0;
				            $v['db_default'] = 0;
				            $v['db_unsigned'] = true;                    
                        break;
                    case 'dictionary':
						$v['db_type'] = 'char';
                        $v['db_isNull'] = 0;
                    break;                    
                }
            }
        } unset($v);   
         
        
    }
    /**
     * Get search fields from DBO config
     * @return array
     */
    public function getSearchFields(){
        $fields = array();
        foreach ($this->_config['fields'] as $k=>$v)
            if(isset($v['is_search']) && $v['is_search'])
                $fields[] = $k;
        return $fields;
    }
    /**
     * Get config item
     * @param string $key
     */
    public function get($key){
        return $this->_config->get($key);
    } 
    /**
     * Check if object is under revision control
     */
    public function isRevControl(){
         if(isset($this->_config['rev_control']) && $this->_config['rev_control'])
            return true;
         else 
             return false;   
    }
    /**
     * Get table indexes
     * @property boolean $includeSystem -optional default = true
     * @return array
     */
    public function getIndexesConfig($includeSystem = true){
    	if($this->_config->offsetExists('indexes'))
    	{
    		if($includeSystem)
    			return $this->_config->get('indexes');
    			
    		$indexes = $this->_config->get('indexes');
    		if(isset($indexes['PRIMARY']))
    			unset($indexes['PRIMARY']);
    		return $indexes;	
    	}
    	else 
    	{
    		return array();	
    	}
    }    
    /**
     * Get field config
     * @param string $field
     * @throws Exception
     * @return array
     */
    public function getFieldConfig($field){
        if(!isset($this->_config['fields'][$field]))
              throw new Exception('Invalid field name: ' . $field);
              
        return $this->_config['fields'][$field]; 
    }
    /**
     * Get config for all fields
     * @property boolean $includeSystem -optional default = true
     * @return array
     */
    public function getFieldsConfig($includeSystem=true){
    	if($includeSystem)
    		return $this->_config['fields'];
    		
    	if(!$includeSystem){
    		$fields = $this->_config['fields'];
    		unset($fields['id']);
    		
    		if($this->isRevControl())
    			return  array_diff_key($fields,  self::$_vcFields);
    		else 
    			return  $fields;
    	}	
    }
    /**
     * Check if field is multy link
     * @param string $field
     * @throws Exception
     * @return boolean
     */
    public function isMultiLink($field){
        $cfg = $this->getFieldConfig($field);                
        if(isset($cfg['type']) && $cfg['type']==='link' && is_array($cfg['link_config']) && $cfg['link_config']['link_type']==='multy')
           return true;
        else 
           return false;    
    }
    /**
     * Get linked object
     * @param string $field
     * @return string  or false on error
     */
    public function getLinkedObject($field){
    	if(!$this->isLink($field))
    		return false;
    	$cfg = $this->getFieldConfig($field);   	
    	return 	$cfg['link_config']['object'];
    }
    /**
     * Check if field is link
     * @param string $field
     * @return boolean
     */
    public function isLink($field){
        $cfg = $this->getFieldConfig($field);
        if(isset($cfg['type']) && $cfg['type']==='link')
            return true;
        else 
            return false;    
    }
    /**
     * Check if field is dictionary link
     * @param string $field
     * @return boolean
     */
    public function isDictionaryLink($field){
        $cfg = $this->getFieldConfig($field);
        if(isset($cfg['type']) && $cfg['type']==='link' && is_array($cfg['link_config']) && $cfg['link_config']['link_type']==='dictionary')
            return true;
        else 
            return false;    
    }
    public function getDbType($field){
    	 $cfg = $this->getFieldConfig($field);
    	 return $cfg['db_type'];
    }
    /**
     * Get list of external links
     * @return array  field => link_config
     */
    public function getLinks()
    {
    	$data = array();
    	$fields = $this->getFieldsConfig();
    	$linkTypes = array('object','multy');
    	foreach ($fields as $name=>$cfg) {
    		if(isset($cfg['type']) && $cfg['type']==='link'
    		 	&& isset($cfg['link_config']['link_type'])
    		 	&& in_array($cfg['link_config']['link_type'], $linkTypes , true)
    		    && isset($cfg['link_config']['object'])
    		){
    		   $data[$cfg['link_config']['object']][$name] = $cfg['link_config']['link_type'];
    		}	
    	}
    	return $data;
    }
    /**
     * Check if field is unique
     * @param string $name
     * @return boolen
     */
    public function isUnique($name){
        if(isset($this->_config['fields'][$name]['unique']) && strlen($this->_config['fields'][$name]['unique']))
           $this->_config['fields'][$name]['unique'];
        else 
            return false;     
    }
    /**
     * Check if field is boolean
     * @param string $field
     * @return boolean
     */
    public function isBoolean($field){    
        return (isset($this->_config['fields'][$field]['db_type']) &&  $this->_config['fields'][$field]['db_type'] === 'boolean'); 
    }   
    public function isText($field){
       return (isset($this->_config['fields'][$field]['db_type']) && in_array($this->_config['fields'][$field]['db_type'] , Db_Object_Builder::$textTypes , true));
    }
    /**
     * Check if field is required
     * @param string $field
     * @return boolean
     */
    public function isRequired($field){
         if(isset($this->_config['fields'][$field]['required']) &&  $this->_config['fields'][$field]['required'])
            return true;
        else 
            return false; 
    }
    /**
     * Check if field exists
     * @param string $field
     */
    public function fieldExists($field){
        return isset($this->_config['fields'][$field]);
    }   
	/**
     * Get field validator
     * @param string  $field
     * @return mixed  string class name / boolean false
     */
    public function getValidator($field){
        if(!$this->fieldExists($field))
                throw new Exception('Invalid property name');
                
        if(isset($this->_config['fields'][$field]['validator']) && !empty($this->_config['fields'][$field]['validator']))
            return $this->_config['fields'][$field]['validator'];              
        else 
            return false;                       
    }
    /**
     * Conver into array
     * @return array
     */
    public function __toArray(){
        return $this->_config->__toArray();
    }
    /**
     * Get name of field which be used to set title for the multilink property
     * @return string
     */
    public function getLinkTitle(){
        if(isset($this->_config['link_title']))
            return $this->_config['link_title'];
        else
            return '';
    }
    /**
     * Check if transactions available
     * @return boolean
     */
    public function isTransact(){
    	if(strtolower($this->_config->get('engine'))=='innodb')
    		return true;
    	else 
    		return false;
    }
    /**
     * Rewrite Object config
     */
    public function save(){
    	$fields = $this->getFieldsConfig(false);
    	$indexes = $this->getIndexesConfig(false);
    	$this->_config->set('fields', $fields);
    	$this->_config->set('indexes' , $indexes);
    	return $this->_config->save();
    }
    /**
     * Set new config for field
     * @param string $field
     * @param array $config
     */
    public function setFieldconfig($field , array $config){
    	$fields = $this->getFieldsConfig();
    	$fields[$field] = $config;
    	$this->_config->set('fields', $fields);
    }
    /**
     * Set new config for index
     * @param string $index
     * @param array $config
     */
    public function setIndexConfig($index , array $config){
    	$indexes = $this->getIndexesConfig();
    	$indexes[$index] = $config;
    	$this->_config->set('indexes', $indexes);
    }
    /**
     * Rename field and rebuild Db Table
     * @param string $oldname
     * @param string $newName
     * @return boolean
     */
    public function renameField($oldName , $newName)
    {
    	$fields = $this->getFieldsConfig();
    	$cfg = $fields[$oldName];
    	unset($fields[$oldName]);
    	$fields[$newName] = $cfg;
    	$this->_config->set('fields', $fields);
    	
    	
    	$indexes = $this->getIndexesConfig();
    	/**
    	 * Check for indexes for field
    	 */
    	foreach ($indexes as $index => &$config)
    	{
    		if(isset($config['columns']) && !empty($config['columns']))
    		{
    			/*
    			 * Rename index link
    			 */
    			foreach ($config['columns'] as $id => &$value){
    				if($value === $oldName){
    					$value = $newName;
    				}
    			}unset($value);
    		}
    	}
    	$this->_config->set('indexes', $indexes);
    	
    	
    	return Db_Object_Builder::getInstance()->renameField($this, $oldName , $newName);
    }
    /**
     * Remove object field
     * @param string $name
     */
    public function removeField($name){
    	$fields = $this->getFieldsConfig();
    	if(!array_key_exists($name, $fields))
    		return;
    	unset($fields[$name]);
    	$this->_config->set('fields' , $fields);	
    	
    	$indexes = $this->getIndexesConfig();
    	/**
    	 * Check for indexes for field
    	 */
    	foreach ($indexes as $index => &$config)
    	{
    		if(isset($config['columns']) && !empty($config['columns']))
    		{
    			/*
    			 * Remove field from index
    			 */
    			foreach ($config['columns'] as $id=>$value){
    				if($value === $name)
    					unset($config['columns'][$id]);
    			}
    			/*
    			 * Remove empty index
    			 */
    			if(empty($config['columns']))
    				unset($indexes[$index]);
    				
    		}
    	}
    	$this->_config->set('indexes', $indexes);
    }
    /**
     * Remove index
     * @param string $name
     */
    public function removeIndex($name){
    	$indexes = $this->getIndexesConfig();
    	if(!array_key_exists($name, $indexes))
    		return;
    	unset($indexes[$name]);
    	$this->_config->set('indexes' , $indexes);
    }
}
